package com.rbac.configuration.secutity.config;


import com.rbac.annotation.Inner;
import com.rbac.configuration.secutity.filter.JwtAuthenticationFilter;
import com.rbac.configuration.secutity.ignore.CustomConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @author zhangliang
 * @version 1.0   2.7版本以后security配置
 * @date 2021/2/2 10:40
 */
@Configuration
@EnableWebSecurity    // 添加 security 过滤器
@EnableGlobalMethodSecurity(prePostEnabled = true)    // 启用方法级别的权限认证
public class WebSecurityConfig {

    protected Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    /**
     * 白名单配置
     */
    private final CustomConfig customConfig;

    @Qualifier("AccessDeniedHandlerImpl")
    private final AccessDeniedHandler accessDeniedHandler;

    private final UserDetailsService customUserDetailsService;

    /**
     * 认证失败处理类
     */
    private final AuthenticationEntryPointImpl unauthorizedHandler;

    private final RequestMappingHandlerMapping requestMappingHandlerMapping;


    public WebSecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomConfig customConfig, AccessDeniedHandler accessDeniedHandler, UserDetailsService customUserDetailsService, AuthenticationEntryPointImpl unauthorizedHandler, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.customConfig = customConfig;
        this.accessDeniedHandler = accessDeniedHandler;
        this.customUserDetailsService = customUserDetailsService;
        this.unauthorizedHandler = unauthorizedHandler;
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
    }


    /**
     * 密码明文加密方式配置
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 获取AuthenticationManager（认证管理器），登录时认证使用
     *
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }


    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and()
                // 基于 token，不需要 csrf
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .logout().disable()
                //Session管理
                .sessionManagement()
                // 因为使用了JWT，所以这里不管理Session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 基于 token，不需要 session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 设置 jwtAuthError 处理认证失败、鉴权失败
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).accessDeniedHandler(accessDeniedHandler).and()
                // rbac动态路由验证
                .authorizeRequests().anyRequest().access("@RBACAuthorityService.hasPermission(request,authentication)")
                .and()
                // 添加 JWT 过滤器，JWT 过滤器在用户名密码认证过滤器之前
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                // 认证用户时用户信息加载配置，注入springAuthUserService
                .userDetailsService(customUserDetailsService);

        return http.build();
    }


    /**
     * 过滤请求
     *
     * @return
     */
    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        return this::configure;
    }

    public void configure(WebSecurity web) {
        WebSecurity and = web.ignoring().and();

        logger.info("web配置白名单");
        if (!ObjectUtils.isEmpty(customConfig.getIgnoreConfig())) {
            //  配置文件读取白名单
            customConfig.getIgnoreConfig().getGet().forEach(url ->
                    and.ignoring().antMatchers(HttpMethod.GET, url));

            // 忽略 POST
            customConfig.getIgnoreConfig().getPost().forEach(url -> and.ignoring().antMatchers(HttpMethod.POST, url));

            // 忽略 DELETE
            customConfig.getIgnoreConfig().getDelete().forEach(url -> and.ignoring().antMatchers(HttpMethod.DELETE, url));

            // 忽略 PUT
            customConfig.getIgnoreConfig().getPut().forEach(url -> and.ignoring().antMatchers(HttpMethod.PUT, url));

            // 忽略 HEAD
            customConfig.getIgnoreConfig().getHead().forEach(url -> and.ignoring().antMatchers(HttpMethod.HEAD, url));

            // 忽略 PATCH
            customConfig.getIgnoreConfig().getPatch().forEach(url -> and.ignoring().antMatchers(HttpMethod.PATCH, url));

            // 忽略 OPTIONS
            customConfig.getIgnoreConfig().getOptions().forEach(url -> and.ignoring().antMatchers(HttpMethod.OPTIONS, url));

            // 忽略 TRACE
            customConfig.getIgnoreConfig().getTrace().forEach(url -> and.ignoring().antMatchers(HttpMethod.TRACE, url));

            // 按照请求格式忽略
            customConfig.getIgnoreConfig().getPattern().forEach(url -> and.ignoring().antMatchers(url));

        }

        // 注解白名单放行
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        handlerMethods.forEach((info, method) -> {
            Set<PathPattern> patterns = Objects.requireNonNull(info.getPathPatternsCondition()).getPatterns();
            // 带IgnoreAuth注解的方法直接放行
            if (!ObjectUtils.isEmpty(method.getMethodAnnotation(Inner.class))) {
                // 根据请求类型做不同的处理
                info.getMethodsCondition().getMethods().forEach(requestMethod -> {
                    switch (requestMethod) {
                        // getPatternsCondition得到请求url数组，遍历处理
                        // 放行
                        case GET:
                            patterns.forEach(pattern -> and.ignoring().antMatchers(HttpMethod.GET, pattern.getPatternString()));
                            break;
                        case POST:
                            patterns.forEach(pattern -> and.ignoring().antMatchers(HttpMethod.POST, pattern.getPatternString()));
                            break;
                        case DELETE:
                            patterns.forEach(pattern -> and.ignoring().antMatchers(HttpMethod.DELETE, pattern.getPatternString()));
                            break;
                        case PUT:
                            patterns.forEach(pattern -> and.ignoring().antMatchers(HttpMethod.PUT, pattern.getPatternString()));
                            break;
                        default:
                            break;
                    }
                });
            }
        });


    }
}
